﻿using Ndns.Packet.Abstractions;
using System.Buffers;
using System.Collections.Generic;
using System.IO;

namespace Ndns.Packet.Parser
{
    class DnsHostNameParser
    {
        private readonly ArrayPool<byte> _arrayPool;

        public DnsHostNameParser() : this(null) { }
        public DnsHostNameParser(ArrayPool<byte> arrayPool)
        {
            _arrayPool = arrayPool;
        }


        public bool TryParse(Stream stream, IDictionary<DnsHostNamePointer, DnsHostName> symbols, ref int offset, out DnsHostName hostName)
        {
            var builder = new DnsHostNameBuilder();
            var newSymbols = new List<DnsHostNameBuilderWithPointer>();

            using (var bufferLease = _arrayPool.LeaseOrAllocate(256))
            {
                var buffer = bufferLease.Array;
                
                while (true)
                {
                    // 读取长度字节
                    int len = stream.ReadByte();
                    if (!PacketEndOfStreamException.CheckValue(len))
                    {
                        hostName = default(DnsHostName);
                        return false;
                    }
                    offset++;

                    byte bLen = unchecked((byte)len);
                    // 检查是否结束
                    if (len == 0)
                    {
                        break;
                    }
                    // 检查是否为指针
                    if (DnsHostNamePointer.IsPointer(bLen))
                    {
                        // 获取指针第二位
                        int pot = stream.ReadByte();
                        if (!PacketEndOfStreamException.CheckValue(pot))
                        {
                            hostName = default(DnsHostName);
                            return false;
                        }
                        offset++;

                        // 计算指针位置
                        var pointer = DnsHostNamePointer.Create(bLen, unchecked((byte)pot));
                        // 查找指针表
                        if (!symbols.TryGetValue(pointer, out var pBuilder))
                        {
                            hostName = default(DnsHostName);
                            return false;
                        }
                        // 拼接并返回
                        AppendToBuilderAndSymbols(builder, newSymbols, offset - 2, pBuilder);
                        break;
                    }
                    // 不是指针，读取这一部分内容
                    len = stream.CopyBytesTo(buffer, bLen);
                    if (len != bLen)
                    {
                        hostName = default(DnsHostName);
                        return false;
                    }

                    // 转换并添加
                    string domainPart = PunycodeHelper.DecodeDomainPart(buffer, len);
                    AppendToBuilderAndSymbols(builder, newSymbols, offset - 1, domainPart);
                    offset += len;
                }
            }
            // 将builder中的指针加入符号表
            foreach (var symbol in newSymbols)
            {
                symbols.Add(symbol.Pointer, symbol.Builder.ToHostName());
            }
            hostName = builder.ToHostName();
            return true;
        }

        private void AppendToBuilderAndSymbols(DnsHostNameBuilder builder, IList<DnsHostNameBuilderWithPointer> symbols, int offset, IEnumerable<string> domainParts)
        {
            builder.Append(domainParts);
            foreach (var item in symbols)
            {
                item.Builder.Append(domainParts);
            }
            symbols.Add(new DnsHostNameBuilderWithPointer((DnsHostNamePointer)unchecked((ushort)offset), new DnsHostNameBuilder(domainParts)));
        }
        private void AppendToBuilderAndSymbols(DnsHostNameBuilder builder, IList<DnsHostNameBuilderWithPointer> symbols, int offset, string domainPart)
        {
            builder.Append(domainPart);
            foreach (var item in symbols)
            {
                item.Builder.Append(domainPart);
            }
            symbols.Add(new DnsHostNameBuilderWithPointer((DnsHostNamePointer)unchecked((ushort)offset), new DnsHostNameBuilder(domainPart)));
        }


        private struct DnsHostNameBuilderWithPointer
        {
            public DnsHostNamePointer Pointer { get; }
            public DnsHostNameBuilder Builder { get; }

            public DnsHostNameBuilderWithPointer(DnsHostNamePointer pointer, DnsHostNameBuilder builder)
            {
                Pointer = pointer;
                Builder = builder;
            }
        }
    }

}
